@namespace Oqtane.Modules.Controls
@using System.Text.Json
@inherits ModuleControlBase
@inject IRoleService RoleService
@inject IUserService UserService
@inject IUserRoleService UserRoleService
@inject IStringLocalizer<PermissionGrid> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer

@if (_permissions != null)
{
    <div class="container">
        <div class="row">
            <div class="col">
                <table class="table table-borderless">
                    <tbody>
                        <tr>
                            <th scope="col">@Localizer["Role"]</th>
                            @foreach (var permissionname in _permissionnames)
                            {
                                <th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
                            }
                        </tr>
                        @foreach (Role role in _roles)
                        {
                            <tr>
                                <td>@role.Name</td>
                                @foreach (var permissionname in _permissionnames)
                                {
                                    <td style="text-align: center;">
                                        <TriStateCheckBox Value="@GetPermissionValue(permissionname, role.Name, -1)" Disabled="@GetPermissionDisabled(permissionname, role.Name)" OnChange="@(e => PermissionChanged(e, permissionname, role.Name, -1))" />
                                    </td>
                                }
                            </tr>
                        }
                    </tbody>
                </table>
                <br />
            </div>
        </div>
        <div class="row">
            <div class="col">
                @if (_users.Count != 0)
                {
                    <div class="row">
                        <div class="col">
                        </div>
                    </div>
                    <table class="table table-borderless">
                        <thead>
                            <tr>
                                <th scope="col">@Localizer["User"]</th>
                                @foreach (var permissionname in _permissionnames)
                                {
                                    <th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
                                }
                            </tr>
                        </thead>
                        <tbody>
                            @foreach (User user in _users)
                            {
                                <tr>
                                    <td>@user.DisplayName (@user.Username)</td>
                                    @foreach (var permissionname in _permissionnames)
                                    {
                                        <td style="text-align: center; width: 1px;">
                                            <TriStateCheckBox Value="@GetPermissionValue(permissionname, "", user.UserId)" Disabled="@GetPermissionDisabled(permissionname, "")" OnChange="@(e => PermissionChanged(e, permissionname, "", user.UserId))" />
                                        </td>
                                    }
                                </tr>
                            }
                        </tbody>
                    </table>
                    <br />
                }
            </div>
        </div>
        <div class="row">
            <div class="col-11">
                <AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["Username.Enter"]" @ref="_user" />
            </div>
            <div class="col-1">
                <button type="button" class="btn btn-primary" @onclick="AddUser">@SharedLocalizer["Add"]</button>
            </div>
        </div>
        <div class="row">
            <div class="col">
                <ModuleMessage Type="MessageType.Warning" Message="@_message" />
            </div>
        </div>
    </div>
}

@code {
    private List<string> _permissionnames;
    private List<Permission> _permissions;
    private List<Role> _roles;
    private List<User> _users = new List<User>();
    private AutoComplete _user;
    private string _message = string.Empty;

    [Parameter]
    public string EntityName { get; set; }

    [Parameter]
    public string PermissionNames { get; set; }

    [Parameter]
    public string Permissions { get; set; } // deprecated - use PermissionList instead

    [Parameter]
    public List<Permission> PermissionList { get; set; }

    protected override async Task OnInitializedAsync()
    {
        if (!string.IsNullOrEmpty(Permissions))
        {
            PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);			
        }

        _roles = await RoleService.GetRolesAsync(ModuleState.SiteId, true);
        _roles.RemoveAll(item => item.Name == RoleNames.Host); // remove host role

        // get permission names
        if (string.IsNullOrEmpty(PermissionNames))
        {
            _permissionnames = new List<string>();
            _permissionnames.Add(Shared.PermissionNames.View);
            _permissionnames.Add(Shared.PermissionNames.Edit);
        }
        else
        {
            _permissionnames = PermissionNames.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();
        }

        // initialize permissions
        _permissions = new List<Permission>();
        if (PermissionList != null && PermissionList.Any())
        {
            foreach (var permission in PermissionList)
            {
                _permissions.Add(permission);
                if (permission.UserId != null)
                {
                    if (!_users.Any(item => item.UserId == permission.UserId.Value))
                    {
                        _users.Add(await UserService.GetUserAsync(permission.UserId.Value, ModuleState.SiteId));
                    }
                }
            }
        }
        else
        {
            foreach (string permissionname in _permissionnames)
            {
                // permission names can be in the form of "EntityName:PermissionName:Roles"
                if (permissionname.Contains(":"))
                {
                    var segments = permissionname.Split(':');
                    if (segments.Length == 3)
                    {
                        foreach (var role in segments[2].Split(';'))
                        {
                            _permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], role, null, true));
                        }
                        // ensure admin access
                        if (!_permissions.Any(item => item.EntityName == segments[0] && item.PermissionName == segments[1] && item.RoleName == RoleNames.Admin))
                        {
                            _permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], RoleNames.Admin, null, true));
                        }
                    }
                }
                else
                {
                    _permissions.Add(new Permission(ModuleState.SiteId, EntityName, permissionname, RoleNames.Admin, null, true));
                }
            }
        }
    }

    private string GetPermissionName(string permissionName)
    {
        return (permissionName.Contains(":")) ? permissionName.Split(':')[1] : permissionName;
    }

    private string GetEntityName(string permissionName)
    {
        return (permissionName.Contains(":")) ? permissionName.Split(':')[0] : EntityName;
    }

    private string DisplayPermissionName(string permissionName)
    {
        var name = Localizer[GetPermissionName(permissionName)].ToString();
        name += " " + Localizer[GetEntityName(permissionName)].ToString();
        return name;
    }

    private bool? GetPermissionValue(string permissionName, string roleName, int userId)
    {
        bool? isauthorized = null;
        if (roleName != "")
        {
            var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
            if (permission != null)
            {
                isauthorized = permission.IsAuthorized;
            }
        }
        else
        {
            var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
            if (permission != null)
            {
                isauthorized = permission.IsAuthorized;
            }			
        }
        return isauthorized;
    }

    private bool GetPermissionDisabled(string permissionName, string roleName)
    {
        var disabled = false;

        // administrator role permissions can only be changed by a host
        if (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
        {
            disabled = true;
        }

        // API permissions can only be changed by an administrator
        if (GetEntityName(permissionName) != EntityName && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
        {
            disabled = true;
        }

        return disabled;
    }

    private bool? PermissionChanged(bool? value, string permissionName, string roleName, int userId)
    {
        if (roleName != "")
        {
            var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
            if (permission != null)
            {
                _permissions.Remove(permission);
            }

            // system roles cannot be denied - only custom roles can be denied
            var role = _roles.FirstOrDefault(item => item.Name == roleName);
            if (value != null && !value.Value && role.IsSystem)
            {
                value = null;
            }

            if (value != null)
            {
                _permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), roleName, null, value.Value));
            }
        }
        else
        {
            var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
            if (permission != null)
            {
                _permissions.Remove(permission);
            }
            if (value != null)
            {
                _permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), null, userId, value.Value));
            }
        }
        return value;
    }

	private async Task<Dictionary<string, string>> GetUsers(string filter)
	{
		var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
        return users.Where(item => item.User.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase) || item.User.Username.Contains(filter, StringComparison.OrdinalIgnoreCase))
			.ToDictionary(item => item.UserId.ToString(), item => item.User.DisplayName + " (" + item.User.Username + ")");
	}

	private async Task AddUser()
	{
		if (!string.IsNullOrEmpty(_user.Key))
		{
			var user = await UserService.GetUserAsync(int.Parse(_user.Key), ModuleState.SiteId);
			if (user != null && !_users.Any(item => item.UserId == user.UserId))
			{
				_users.Add(user);
			}
		}
		else
		{
			_message = Localizer["Message.Username.DontExist"];
		}
		_user.Clear();
	}

	public string GetPermissions()
	{
		ValidatePermissions();
		return JsonSerializer.Serialize(_permissions);
	}

	public List<Permission> GetPermissionList()
	{
		ValidatePermissions();
		return _permissions;
	}

	private void ValidatePermissions()
	{
		if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
		{
            // remove host role permissions
            var permissions = _permissions.Where(item => item.RoleName == RoleNames.Host).ToList();
            foreach (var permission in permissions)
            {
            	_permissions.Remove(permission);
            }
            // add host role permissions if administrator role is not assigned (to prevent lockout)
            foreach (var permissionname in _permissionnames)
			{
				if (!_permissions.Any(item => item.EntityName == GetEntityName(permissionname) && item.PermissionName == GetPermissionName(permissionname) && item.RoleName == RoleNames.Admin))
				{
					_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionname), GetPermissionName(permissionname), RoleNames.Host, null, true));
				}
			}
		}
    }
}
